function Calendar(repository, calendarViewBody) {
    this._repository = repository;
    this._history = new CalendarHistory();
    this._calendarViewBody = calendarViewBody;
    this._currentYearMonth = null;
    this._updateCallbacks = [];
    this._lastUsedLocationCalculator = new LastUsedLocationCalculator(this._repository);
}

Calendar.prototype = {
    showMonth: function(yearMonth) {
        this._currentYearMonth = yearMonth;
        this._currentMonthModel = this._getCurrentMonthModel(this._repository.getMonthByYearMonth(yearMonth));
        this._repository.flushMonths();
        
        this._calendarViewBody.setMonthModel(this._currentMonthModel);
        this._calendarViewBody.render();
        if (this._updateCallbacks.length > 0) {
            this._updateCallbacks.foreach(function(updateCallback) {
                updateCallback();
            })
        }
        this._preloadSiblingYears(yearMonth.year());
    },

    getShowingYearMonth: function() {
        return this._currentYearMonth;
    },

    _refresh: function() {
        this.showMonth(this._currentYearMonth);
        if (this._refreshCallback) {
            this._refreshCallback();
        }
    },
    _preloadSiblingYears: function(year) {
        this._repository.preloadYear(year + 1);
        this._repository.preloadYear(year - 1);
        this._repository.preloadYear(year + 2);
        this._repository.preloadYear(year - 2);
    },

    _getCurrentMonthModel: function(monthModel) {
        var yearMonth = monthModel.getYearMonth();

        var previousYearMonth = yearMonth.addMonths(-1);
        var nextYearMonth = yearMonth.addMonths(1);

        var previousMonthModel = this._repository.getMonthByYearMonth(previousYearMonth);
        var nextMonthModel = this._repository.getMonthByYearMonth(nextYearMonth);

        var previousMonthDays = [];
        var nextMonthDays = [];

        monthModel.getFullDays().foreach(function(day) {
            if (day.isInMonth(previousYearMonth)) {
                previousMonthDays.push(previousMonthModel.getDay(day.getDate()));
                return;
            }
            if (day.isInMonth(nextYearMonth)) {
                nextMonthDays.push(nextMonthModel.getDay(day.getDate()));
                return;
            }
        });

        monthModel.setInactiveDays(previousMonthDays, nextMonthDays);
        return monthModel;
    },

    //action
    applyAction: function(action) {
        var me = this;
        action.apply(this._repository, function(months, travel) {
            me.recordHistory(months, travel)
        });

        this._currentMonthModel = this._getCurrentMonthModel(this._repository.getMonthByYearMonth(this._currentYearMonth));

        this._calendarViewBody.setMonthModel(this._currentMonthModel);

        if (action.applyUI) {
            action.applyUI(this._calendarViewBody);
        }

        if (this._refreshCallback) {
            this._refreshCallback();
        }

        // async to prevent flushMonths twice if apply two actions in a row.
        pwc.registerOneTimeFunction(me, function(){me._repository.flushMonths()});
    },
    
    hasConflict: function(action) {
        return action.hasConflict(this._repository);
    },
    getDay: function(date) {
        return this._currentMonthModel.getDay(date);
    },

    //events
    onUpdate: function(callback) {
        this._updateCallbacks.push(callback);
    },

    onRefresh: function(callback) {
        this._refreshCallback = callback;
    },

    //undo
    onUndoStatusChange: function(callback) {
        this._undoStatusChangeCallback = callback;
    },

    _onUndoStatusChange: function() {
        if (this._undoStatusChangeCallback) {
            this._undoStatusChangeCallback(!this._history.isEmpty());
        }
    },
    recordHistory: function(months, travelChange) {
        this._history.add(months, travelChange);
        this._onUndoStatusChange();
    },


    undo: function() {
        var me = this;
        var undoItems = this._history.getLastTransaction();
        var undoAction = function(item) {
            if (item.hasMonth()) {
                var months = item.getMonths().select(function(monthDto) {
                    return new Month(monthDto.year, monthDto.month, [], [], monthDto);
                });
                me._repository.markAsChanged(months);
            }
            if (item.hasTravel()) {
                me._repository.saveTravelSteps(item.getTravel());
            }
        };

        undoItems.foreach(undoAction);
        this._onUndoStatusChange();
        this._refresh();
    },

    beginTransaction: function() {
        this._history.beginTransaction();
    },

    // last used locations
    getLastUsedLocations: function(date) {
        if (!date) {
            var currentYearMonth = this.getShowingYearMonth();
            date = currentYearMonth.lastDayOfMonth();
        }

        return this._lastUsedLocationCalculator.getLastUsedLocationsFor(date);
    }
};
